//
//  XcodeProjBuilder.swift
//  JKTools
//
//  Created by 姜奎 on 4/15/25.
//

import Foundation
import XcodeProj
import PathKit

class XcodeProjBuilder {

    let path: Path

    let name: String

    let dependencyBuilder: XcodeProjBuilder?

    lazy var targetName = "Module-\(name)"

    lazy var libName = "Module_\(name)"

    lazy var dependency: String = {
        var dependency = "#import <\(name)/\(name).h>"
        guard let dependencyBuilder = dependencyBuilder else { return dependency }
        dependency = dependency + "\n#import <\(dependencyBuilder.libName)/\(dependencyBuilder.targetName).h>"
        return dependency
    }()

    let currentTime: String = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy/M/d"
        let currentDate = Date()
        let formattedDate = dateFormatter.string(from: currentDate)
        return formattedDate
    }()

    init(path: Path, name: String, dependency builder: XcodeProjBuilder? = nil) {
        self.path = path
        self.name = name
        self.dependencyBuilder = builder
    }

    lazy var publicHeader: String = {
        let publicHeader = """
    //
    //  \(targetName).h
    //  \(targetName)
    //
    //  Created by SubModule Job on \(currentTime).
    //
    
    #import <Foundation/Foundation.h>
    
    //! Project version number for \(libName).
    FOUNDATION_EXPORT double \(libName)VersionNumber;
    
    //! Project version string for \(libName).
    FOUNDATION_EXPORT const unsigned char \(libName)VersionString[];
    
    // In this header, you should import all the public headers of your framework using statements like #import <\(libName)/PublicHeader.h>
    
    \(dependency)
    """
        return publicHeader
    }()

    lazy var privateImp: String = {
        let privateImp = """
    //
    //  \(targetName)-dummy.m
    //  \(targetName)
    //
    //  Created by SubModule Job on \(currentTime).
    //
    
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface \(libName)_dummy : NSObject
    
    @end
    
    NS_ASSUME_NONNULL_END
    @implementation \(libName)_dummy
    
    @end
    """
        return privateImp
    }()
}

extension XcodeProjBuilder {

    func generateXcodeproj(with project: Project) throws {
        guard let debuggingXcodeprojPath = project.debuggingXcodeprojPath, let xcodeProj = try project.copyXcodeproj() else { return }
        let modulesFilesGroup = PBXGroup(sourceTree: .group, name: "SubModules")
        xcodeProj.pbxproj.add(object: modulesFilesGroup)
        xcodeProj.pbxproj.rootObject?.mainGroup?.children.append(modulesFilesGroup)
        project.recordList.forEach { record in
            let subProject = Project(directoryPath: "\(project.checkoutsPath)/\(record)")
            let subDebuggingXcodeProj = try? subProject.copyXcodeproj()
            if let subDebuggingXcodeProj = subDebuggingXcodeProj,
               let subDebuggingXcodeProjPath = subProject.debuggingXcodeprojPath {

                let subModulesFilesGroup = PBXGroup(sourceTree: .group, name: "SubModules")
                subDebuggingXcodeProj.pbxproj.add(object: subModulesFilesGroup)
                subDebuggingXcodeProj.pbxproj.rootObject?.mainGroup?.children.append(subModulesFilesGroup)

                subProject.recordList.forEach { record in
                    let recordProject = Project(directoryPath: "\(project.checkoutsPath)/\(record)")
                    subDebuggingXcodeProj.addDependencySubModuleTargets(with: recordProject, in: subModulesFilesGroup, is: false)
                }
                po(tip: "【\(subProject.directoryEntrance)】写入*-Debugging.xcodeproj文件")
                try? subDebuggingXcodeProj.write(path: subDebuggingXcodeProjPath, override: true)
                try? (subDebuggingXcodeProjPath + "xcshareddata").delete()
                try? (subDebuggingXcodeProjPath + "xcuserdata").delete()
                xcodeProj.addDependencySubModuleTargets(with: subProject, in: modulesFilesGroup, is: true)
            }
        }
        po(tip: "【\(project.directoryEntrance)】写入*-Debugging.xcodeproj文件")
        try xcodeProj.write(path: debuggingXcodeprojPath, override: true)
        try? (debuggingXcodeprojPath + "xcshareddata").delete()
        try? (debuggingXcodeprojPath + "xcuserdata").delete()

        guard let debuggingWorkSpacePath = project.debuggingWorkSpacePath, let workspace = try project.copyXcworkspace() else { return }
        workspace.addFileGroup(with: project)
        po(tip: "【\(project.directoryEntrance)】写入*-Debugging.xcworkspace文件")
        try workspace.write(path: debuggingWorkSpacePath, override: true)
        try? (debuggingWorkSpacePath + "xcshareddata").delete()
        try? (debuggingWorkSpacePath + "xcuserdata").delete()
    }

/*
    func generateXcodeproj1(with project: Project) throws {

        let rootProjPath = path + "Module.xcodeproj"
        let rootPbxprojPath = rootProjPath + "project.pbxproj"
        var rootPbxproj = try? PBXProj(path: rootPbxprojPath)

        if rootPbxproj == nil {
            po(tip: "\(rootPbxprojPath)文件不存在，需要重新创建！", type: .warning)
            rootPbxproj = PBXProj()
        }

        guard let rootPbxproj = rootPbxproj else { return }

        let rootTarget = rootTarget(with: rootPbxproj)

        var lastSubTarget: PBXNativeTarget? = nil
        var lastBulider: XcodeProjBuilder? = nil

        project.recordList.forEach { name in

            let subProject = Project(directoryPath: "\(project.checkoutsPath)/\(name)")

            let subBuilder = XcodeProjBuilder(path: path, name: name, dependency: lastBulider)

            if let sub = Self.createAndDependencySubModuleTargets(with: rootPbxproj, by: subProject, and: subBuilder, and: rootTarget) {

                if let lastSubTarget = lastSubTarget, let dependency = try? sub.target.addDependency(target: lastSubTarget) {
                    rootPbxproj.add(object: dependency)
                }

                lastSubTarget = sub.target
                lastBulider = subBuilder
            }
        }

        if let lastSubTarget = lastSubTarget, let dependency = try? rootTarget.addDependency(target: lastSubTarget) {
            rootPbxproj.add(object: dependency)
        }

        let workspace = XCWorkspace()
        let proj = XcodeProj(workspace: workspace, pbxproj: rootPbxproj)

        try proj.write(path: rootProjPath, override: true)
    }

    func rootTarget(with pbxproj: PBXProj) -> PBXNativeTarget {
        return PBXNativeTarget.createRootTarget(with: pbxproj, by: self)
    }

    static func createAndDependencySubModuleTargets(with pbxproj: PBXProj, by subProject: Project, and builder: XcodeProjBuilder, and rootTarget: PBXNativeTarget) -> (target: PBXNativeTarget,refTarget: PBXNativeTarget,fileRef: PBXFileReference)? {

        guard let filePbxproj = subProject.xcodeProj?.pbxproj else {
            return nil
        }
//,.dynamicLibrary,.staticLibrary
        let productTypes: [PBXProductType] = [.framework,.staticFramework]

        let fileTarget = filePbxproj.nativeTargets.first { target in
            productTypes.contains(target.productType ?? .none)
        }

        guard let fileTarget = fileTarget else { return nil }

        let target = PBXNativeTarget.createTarget(with: pbxproj, and: builder, by: rootTarget)

        pbxproj.add(object: target)

        let fileRef = PBXFileReference(sourceTree: .group, name: subProject.entrance, lastKnownFileType: "wrapper.pb-project", path: "\(Path(subProject.rootProject.checkoutsPath).lastComponent)/\(builder.name)/\(builder.name).xcodeproj")
        pbxproj.add(object: fileRef)

        let proxy = PBXContainerItemProxy(containerPortal: .fileReference(fileRef), remoteGlobalID: .object(fileTarget), proxyType: .nativeTarget, remoteInfo: builder.name)
        pbxproj.add(object: proxy)

        let dependency = PBXTargetDependency(name: builder.name, targetProxy: proxy)
        pbxproj.add(object: dependency)
        target.dependencies.append(dependency)

        pbxproj.rootObject?.mainGroup?.children.append(fileRef)
        pbxproj.rootObject?.projects.append(["ProjectRef": fileRef])

        return (target, fileTarget, fileRef)
    }
 */
}
/*
extension PBXNativeTarget {

    static func createRootTarget(with pbxproj: PBXProj, by builder: XcodeProjBuilder) -> PBXNativeTarget {

        let target = pbxproj.nativeTargets.first { target in
            return target.name == builder.targetName
        } ?? PBXNativeTarget(name: builder.targetName, productName: builder.targetName, productType: .staticFramework)

        pbxproj.forEach { object in
            pbxproj.delete(object: object)
        }
        pbxproj.add(object: target)

        let mainGroup = PBXGroup(sourceTree: .group)
        pbxproj.add(object: mainGroup)

        let rootObjectBuildConfigurationList = XCConfigurationList()
        pbxproj.add(object: rootObjectBuildConfigurationList)

        _ = try? rootObjectBuildConfigurationList.addDefaultConfigurations()

        rootObjectBuildConfigurationList.buildConfigurations.forEach { config in
            pbxproj.add(object: config)
        }

        let targetProductsGroup = PBXGroup(sourceTree: .group, name: "Products")
        pbxproj.add(object: targetProductsGroup)
        mainGroup.children.append(targetProductsGroup)

        let rootObject = PBXProject(name: builder.name, buildConfigurationList: rootObjectBuildConfigurationList, compatibilityVersion: nil, preferredProjectObjectVersion: nil, minimizedProjectReferenceProxies: 13, mainGroup: mainGroup, productsGroup: targetProductsGroup, targets: [target])
        pbxproj.add(object: rootObject)
        pbxproj.rootObject = rootObject

        let buildConfigurationList = XCConfigurationList()
        pbxproj.add(object: buildConfigurationList)

        let debugBS = BuildSettingsProvider.targetDefault(platform: .iOS, product: .framework).merging(["IPHONEOS_DEPLOYMENT_TARGET": 13], uniquingKeysWith: { $1 })

        let debug = XCBuildConfiguration(name: "Debug",buildSettings: debugBS)
        pbxproj.add(object: debug)
        buildConfigurationList.buildConfigurations.append(debug)

        let releaseBS = BuildSettingsProvider.targetDefault(platform: .iOS, product: .framework).merging(["IPHONEOS_DEPLOYMENT_TARGET": 13], uniquingKeysWith: { $1 })
        let release = XCBuildConfiguration(name: "Release",buildSettings: releaseBS)
        pbxproj.add(object: release)

        buildConfigurationList.buildConfigurations.append(release)

        target.buildConfigurationList = buildConfigurationList

        return target
    }

    static func createTarget(with pbxproj: PBXProj, and builder: XcodeProjBuilder, by rootTarget: PBXNativeTarget) -> PBXNativeTarget {

        let targetPath = builder.path + builder.targetName

        try? (targetPath).delete()

        try? (targetPath).mkpath()

        try? (targetPath + "\(builder.targetName).h").write(builder.publicHeader)
        try? (targetPath + "\(builder.targetName)-dummy.m").write(builder.privateImp)

        let target = PBXNativeTarget(name: builder.targetName, productName: builder.targetName, productType: .staticFramework)
        pbxproj.add(object: target)
        pbxproj.rootObject?.targets.append(target)

        let targetFilesGroup = PBXGroup(sourceTree: .group, path: builder.targetName)
        pbxproj.add(object: targetFilesGroup)
        pbxproj.rootObject?.mainGroup?.children.append(targetFilesGroup)

        let targetFrameworkFile = PBXFileReference(sourceTree: .buildProductsDir, explicitFileType: "wrapper.framework", path: builder.libName + ".framework", includeInIndex: false)
        pbxproj.add(object: targetFrameworkFile)
        target.product = targetFrameworkFile
        pbxproj.rootObject?.productsGroup?.children.append(targetFrameworkFile)

        let publicHeaderFile = PBXFileReference(sourceTree: .group, lastKnownFileType: "sourcecode.c.h", path: builder.targetName + ".h")
        pbxproj.add(object: publicHeaderFile)

        let publicHeaderBuildFile = PBXBuildFile(file: publicHeaderFile)
        pbxproj.add(object: publicHeaderBuildFile)

        let headersBuildPhase = PBXHeadersBuildPhase(files: [publicHeaderBuildFile])
        pbxproj.add(object: headersBuildPhase)

        let privateImpFile = PBXFileReference(sourceTree: .group, lastKnownFileType: "sourcecode.c.objc", path: builder.targetName + "-dummy.m")
        pbxproj.add(object: privateImpFile)

        let privateImpBuildFile = PBXBuildFile(file: privateImpFile)
        pbxproj.add(object: privateImpBuildFile)

        let sourcesBuildPhase = PBXSourcesBuildPhase(files: [privateImpBuildFile])
        pbxproj.add(object: sourcesBuildPhase)

        targetFilesGroup.children.append(publicHeaderFile)
        targetFilesGroup.children.append(privateImpFile)

        target.buildPhases = [headersBuildPhase,sourcesBuildPhase]

        target.buildConfigurationList = rootTarget.buildConfigurationList

        return target
    }
}
 */

extension Project {
    func copyXcodeproj() throws -> XcodeProj? {

        guard let xcodeprojPath = xcodeprojPath,
              let debuggingXcodeprojPath = debuggingXcodeprojPath else { return nil }

        po(tip: "【\(xcodeprojName ?? "")】删除旧*-Debugging.xcodeproj文件")
        try? debuggingXcodeprojPath.delete()
        po(tip: "【\(xcodeprojName ?? "")】复制*-Debugging.xcodeproj文件")
        try xcodeprojPath.copy(debuggingXcodeprojPath)
        po(tip: "【\(xcodeprojName ?? "")】解析*-Debugging.xcodeproj文件")
        guard let debuggingXcodeProj = self.debuggingXcodeProj else { return nil }
        debuggingXcodeProj.userData = []
        debuggingXcodeProj.sharedData = nil
        return debuggingXcodeProj
    }

    func copyXcworkspace() throws -> XCWorkspace? {

        guard let workSpacePath = workSpacePath,
              let debuggingWorkSpacePath = debuggingWorkSpacePath else { return nil }

        po(tip: "【\(xcodeprojName ?? "")】删除旧*-Debugging.xcworkspace文件")
        try? debuggingWorkSpacePath.delete()
        po(tip: "【\(xcodeprojName ?? "")】复制*-Debugging.xcworkspace文件")
        try workSpacePath.copy(debuggingWorkSpacePath)
        po(tip: "【\(xcodeprojName ?? "")】解析*-Debugging.xcworkspace文件")
        guard let debuggingWorkspace = self.debuggingWorkspace else { return nil }
        return debuggingWorkspace
    }
}

extension XcodeProj {
    func addDependencySubModuleTargets(with project: Project, in group: PBXGroup, is rootProject: Bool = true) {
        guard let projectName = project.xcodeprojName,
              let debuggingXcodeprojEntrance = project.debuggingXcodeprojEntrance else { return }

        guard let subXcodeProj = project.xcodeProj else {
            return
        }
            //
        let libProductTypes: [PBXProductType] = [.framework,.staticFramework,.dynamicLibrary,.staticLibrary]

        let fileTarget = subXcodeProj.pbxproj.nativeTargets.first { target in
            libProductTypes.contains(target.productType ?? .none)
        }

        guard let fileTarget = fileTarget else { return }

        let filePath = rootProject ? "\(ModulesConfigs.sharedInstance.config.checkouts)/\(projectName)/\(debuggingXcodeprojEntrance)": "../\(projectName)/\(debuggingXcodeprojEntrance)"


        let fileRef = PBXFileReference(sourceTree: .sourceRoot, name: debuggingXcodeprojEntrance, lastKnownFileType: "wrapper.pb-project", path:filePath)
        self.pbxproj.add(object: fileRef)

        let proxy = PBXContainerItemProxy(containerPortal: .fileReference(fileRef), remoteGlobalID: .object(fileTarget), proxyType: .nativeTarget, remoteInfo: projectName)
        self.pbxproj.add(object: proxy)

        let dependency = PBXTargetDependency(name: projectName, targetProxy: proxy)
        self.pbxproj.add(object: dependency)
        let productTypes: [PBXProductType] = [.framework,.staticFramework,.dynamicLibrary,.staticLibrary, .application]
        let nativeTargets = self.pbxproj.nativeTargets.filter { target in
            productTypes.contains(target.productType ?? .none)
        }

        nativeTargets.forEach { target in
            target.dependencies.append(dependency)
        }

        group.children.append(fileRef)
        self.pbxproj.rootObject?.projects.append(["ProjectRef": fileRef])
    }
}

extension XCWorkspace {
    func addFileGroup(with project: Project) {
        guard let debuggingXcodeprojEntrance = project.debuggingXcodeprojEntrance, let xcodeprojEntrance = project.xcodeprojEntrance else { return }
        self.data.children.removeAll { element in
            return element.location.path == xcodeprojEntrance
        }
        self.data.children.insert(.file(.init(location: .group(debuggingXcodeprojEntrance))), at: 0)
    }
}

